<script lang="tsx">
import type { ComponentSize, ElTooltipProps } from 'element-plus'
import type { CSSProperties, PropType } from 'vue'
import type {
  Pagination,
  TableColumn,
  TableProps,
  TableSetProps,
} from './types'
import { ElImage, ElPagination, ElTable, ElTableColumn } from 'element-plus'
import { useDesign } from 'fetm-hooks'
import { getSlot } from 'fetm-utils'
import { get, set } from 'lodash-es'
import { computed, defineComponent, onMounted, ref, unref, watch } from 'vue'
import { propTypes } from '../../../helpers/propTypes'
import TableActions from './components/TableActions.vue'
import { setIndex } from './helpers'

export default defineComponent({
  // eslint-disable-next-line vue/no-reserved-component-names
  name: 'Table',
  props: {
    pageSize: propTypes.number.def(10),
    currentPage: propTypes.number.def(1),
    // 是否展示表格的工具栏
    showAction: propTypes.bool.def(false),
    // 是否所有的超出隐藏，优先级低于schema中的showOverflowTooltip,
    showOverflowTooltip: propTypes.bool.def(true),
    // 表头
    columns: {
      type: Array as PropType<TableColumn[]>,
      default: () => [],
    },
    // 是否展示分页
    pagination: {
      type: Object as PropType<Pagination>,
      default: (): Pagination | undefined => undefined,
    },
    // 仅对 type=selection 的列有效，类型为 Boolean，为 true 则会在数据更新之后保留之前选中的数据（需指定 row-key）
    reserveSelection: propTypes.bool.def(false),
    // 加载状态
    loading: propTypes.bool.def(false),
    // 是否叠加索引
    reserveIndex: propTypes.bool.def(false),
    // 对齐方式
    align: propTypes.string
      // @ts-ignore
      .validate((v: string) => ['left', 'center', 'right'].includes(v))
      .def('left'),
    // 表头对齐方式
    headerAlign: propTypes.string
      // @ts-ignore
      .validate((v: string) => ['left', 'center', 'right'].includes(v))
      .def('left'),
    data: {
      type: Array as PropType<Record<string, any>[]>,
      default: () => [],
    },
    // 是否自动预览
    preview: {
      type: Array as PropType<string[]>,
      default: () => [],
    },
    height: propTypes.oneOfType([Number, String]),
    maxHeight: propTypes.oneOfType([Number, String]),
    stripe: propTypes.bool.def(false),
    border: propTypes.bool.def(false),
    size: {
      type: String as PropType<ComponentSize>,
      validator: (v: ComponentSize) => ['medium', 'small', 'mini'].includes(v),
    },
    fit: propTypes.bool.def(true),
    showHeader: propTypes.bool.def(true),
    highlightCurrentRow: propTypes.bool.def(false),
    currentRowKey: propTypes.oneOfType([Number, String]),
    // row-class-name, 类型为 (row: Record<string, any>, rowIndex: number) => string | string
    rowClassName: {
      type: [Function, String] as PropType<
        ((row: Record<string, any>, rowIndex: number) => string) | string
      >,
      default: '',
    },
    rowStyle: {
      type: [Function, Object] as PropType<
        ((row: Record<string, any>, rowIndex: number) => Record<string, any>) | CSSProperties
      >,
      default: () => undefined,
    },
    cellClassName: {
      type: [Function, String] as PropType<
        ((row: Record<string, any>, column: any, rowIndex: number) => string) | string
      >,
      default: '',
    },
    cellStyle: {
      type: [Function, Object] as PropType<
        | ((row: Record<string, any>, column: any, rowIndex: number) => Record<string, any>)
        | CSSProperties
      >,
      default: () => undefined,
    },
    headerRowClassName: {
      type: [String, Function] as PropType<
        string | ((row: Record<string, any>, rowIndex: number) => string)
      >,
      default: '',
    },
    headerRowStyle: {
      type: [Function, Object] as PropType<
        ((row: Record<string, any>, rowIndex: number) => Record<string, any>) | CSSProperties
      >,
      default: () => undefined,
    },
    headerCellClassName: {
      type: [Function, String] as PropType<
        ((row: Record<string, any>, column: any, rowIndex: number) => string) | string
      >,
      default: '',
    },
    headerCellStyle: {
      type: [Function, Object] as PropType<
        | ((row: Record<string, any>, column: any, rowIndex: number) => Record<string, any>)
        | CSSProperties
      >,
      default: () => undefined,
    },
    rowKey: propTypes.string.def('id'),
    emptyText: propTypes.string.def('No Data'),
    defaultExpandAll: propTypes.bool.def(false),
    expandRowKeys: {
      type: Array as PropType<string[]>,
      default: () => [],
    },
    defaultSort: {
      type: Object as PropType<{
        prop?: string
        order?: 'ascending' | 'descending'
      }>,
      default: () => ({}),
    },
    tooltipEffect: {
      type: String as PropType<'dark' | 'light'>,
      default: 'dark',
    },
    tooltipOptions: {
      type: Object as PropType<
        Pick<
          ElTooltipProps,
          | 'effect'
          | 'enterable'
          | 'hideAfter'
          | 'offset'
          | 'placement'
          | 'popperClass'
          | 'popperOptions'
          | 'showAfter'
          | 'showArrow'
        >
      >,
      default: () => ({
        enterable: true,
        placement: 'top',
        showArrow: true,
        hideAfter: 200,
        popperOptions: { strategy: 'fixed' },
      }),
    },
    showSummary: propTypes.bool.def(false),
    sumText: propTypes.string.def('Sum'),
    summaryMethod: {
      type: Function as PropType<
        (param: { columns: any[], data: any[] }) => any[]
      >,
      default: () => undefined,
    },
    spanMethod: {
      type: Function as PropType<
        (param: {
          row: any
          column: any
          rowIndex: number
          columnIndex: number
        }) => any[]
      >,
      default: () => undefined,
    },
    selectOnIndeterminate: propTypes.bool.def(true),
    indent: propTypes.number.def(16),
    lazy: propTypes.bool.def(false),
    load: {
      type: Function as PropType<
        (row: Record<string, any>, treeNode: any, resolve: (...args: any[]) => any) => void
      >,
      default: () => undefined,
    },
    treeProps: {
      type: Object as PropType<{
        hasChildren?: string
        children?: string
        label?: string
      }>,
      default: () => ({
        hasChildren: 'hasChildren',
        children: 'children',
        label: 'label',
      }),
    },
    tableLayout: {
      type: String as PropType<'auto' | 'fixed'>,
      default: 'fixed',
    },
    scrollbarAlwaysOn: propTypes.bool.def(false),
    flexible: propTypes.bool.def(false),
  },
  emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
  setup(props, { attrs, emit, slots, expose }) {
    const { getPrefixCls } = useDesign()
    const prefixCls = getPrefixCls('table')
    const elTableRef = ref<InstanceType<typeof ElTable>>()

    // 注册
    onMounted(() => {
      const tableRef = unref(elTableRef)
      emit('register', tableRef?.$parent, elTableRef)
    })

    const pageSizeRef = ref(props.pageSize)

    const currentPageRef = ref(props.currentPage)

    // useTable传入的props
    const outsideProps = ref<TableProps>({})

    const mergeProps = ref<TableProps>({})

    const getProps = computed(() => {
      const propsObj = { ...props }
      Object.assign(propsObj, unref(mergeProps))
      return propsObj
    })

    const setProps = (props: TableProps = {}) => {
      mergeProps.value = Object.assign(unref(mergeProps), props)
      outsideProps.value = { ...props } as any
    }

    const setColumn = (
      columnProps: TableSetProps[],
      columnsChildren?: TableColumn[],
    ) => {
      const { columns } = unref(getProps)
      for (const v of columnsChildren || columns) {
        for (const item of columnProps) {
          if (v.field === item.field)
            set(v, item.path, item.value)
          else if (v.children?.length)
            setColumn(columnProps, v.children)
        }
      }
    }

    const addColumn = (column: TableColumn, index?: number) => {
      const { columns } = unref(getProps)
      if (index)
        columns.splice(index, 0, column)
      else columns.push(column)
    }

    const delColumn = (field: string) => {
      const { columns } = unref(getProps)
      const index = columns.findIndex(item => item.field === field)
      if (index > -1)
        columns.splice(index, 1)
    }

    const refresh = () => {
      emit('refresh')
    }

    const changSize = (size: ComponentSize) => {
      setProps({ size })
    }
    expose({
      setProps,
      setColumn,
      delColumn,
      addColumn,
      elTableRef,
    })

    const pagination = computed(() => {
      return Object.assign(
        {
          small: false,
          background: false,
          pagerCount: 7,
          layout: 'sizes, prev, pager, next, jumper, ->, total',
          pageSizes: [5, 10, 20, 30, 40, 50, 100],
          disabled: false,
          hideOnSinglePage: false,
          total: 10,
        },
        unref(getProps).pagination,
      )
    })

    watch(
      () => unref(getProps).pageSize,
      (val: number) => {
        pageSizeRef.value = val
      },
    )

    watch(
      () => unref(getProps).currentPage,
      (val: number) => {
        currentPageRef.value = val
      },
    )

    watch(
      () => pageSizeRef.value,
      (val: number) => {
        emit('update:pageSize', val)
      },
    )

    watch(
      () => currentPageRef.value,
      (val: number) => {
        emit('update:currentPage', val)
      },
    )

    const getBindValue = computed(() => {
      const bindValue: Record<string, any> = { ...attrs, ...unref(getProps) }
      delete bindValue.columns
      delete bindValue.data
      return bindValue
    })

    const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
      const { align, headerAlign, showOverflowTooltip, preview }
        = unref(getProps)
      return columnsChildren.map((v) => {
        if (unref(v.hidden))
          return null
        const props = { ...v } as any
        if (props.children)
          delete props.children

        const children = v.children

        const slots = {
          default: (...args: any[]) => {
            const data = args[0]
            let isImageUrl = false
            if (preview.length)
              isImageUrl = preview.some(item => (item as string) === v.field)

            return children && children.length
              ? renderTreeTableColumn(children)
              : props?.slots?.default
                ? props.slots.default(...args)
                : v?.formatter
                  ? v?.formatter?.(
                    data.row,
                    data.column,
                    get(data.row, v.field),
                    data.$index,
                  )
                  : isImageUrl
                    ? renderPreview(get(data.row, v.field))
                    : (get(data.row, v.field) ?? '--')
          },
        }
        if (props?.slots?.header) {
          // @ts-ignore
          slots.header = (...args: any[]) => props.slots.header(...args)
        }

        return (
          <ElTableColumn
            showOverflowTooltip={showOverflowTooltip}
            align={align}
            headerAlign={headerAlign}
            {...props}
            prop={v.field}
          >
            {slots}
          </ElTableColumn>
        )
      })
    }

    const renderPreview = (url: string) => {
      return (
        <div class="flex items-center">
          <ElImage
            src={url}
            fit="cover"
            class="h-100px w-[100%]"
            lazy
            preview-src-list={[url]}
            preview-teleported
          />
        </div>
      )
    }

    const renderTableColumn = (columnsChildren?: TableColumn[]) => {
      const {
        columns,
        reserveIndex,
        pageSize,
        currentPage,
        align,
        headerAlign,
        showOverflowTooltip,
        reserveSelection,
        preview,
      } = unref(getProps)

      return (columnsChildren || columns).map((v) => {
        if (unref(v.hidden))
          return null
        if (v.type === 'index') {
          return (
            <ElTableColumn
              type="index"
              index={
                v.index
                  ? v.index
                  : index =>
                    setIndex(reserveIndex, index, pageSize, currentPage)
              }
              align="center"
              headerAlign="center"
              label={v.label}
              width="65"
            >
            </ElTableColumn>
          )
        }
        else if (v.type === 'selection') {
          return (
            <ElTableColumn
              type="selection"
              reserveSelection={reserveSelection}
              align="center"
              headerAlign="center"
              selectable={v.selectable}
              width="50"
            >
            </ElTableColumn>
          )
        }
        else {
          const props = { ...v } as any
          if (props.children)
            delete props.children

          const children = v.children

          const slots = {
            default: (...args: any[]) => {
              const data = args[0]

              let isImageUrl = false
              if (preview.length) {
                isImageUrl = preview.some(
                  item => (item as string) === v.field,
                )
              }

              return children && children.length
                ? renderTreeTableColumn(children)
                : props?.slots?.default
                  ? props.slots.default(...args)
                  : v?.formatter
                    ? v?.formatter?.(
                      data.row,
                      data.column,
                      get(data.row, v.field),
                      data.$index,
                    )
                    : isImageUrl
                      ? renderPreview(get(data.row, v.field))
                      : (get(data.row, v.field) ?? '--')
            },
          }
          if (props?.slots?.header) {
            // @ts-ignore
            slots.header = (...args: any[]) => props.slots.header(...args)
          }

          return (
            <ElTableColumn
              showOverflowTooltip={showOverflowTooltip}
              align={align}
              headerAlign={headerAlign}
              {...props}
              prop={v.field}
            >
              {slots}
            </ElTableColumn>
          )
        }
      })
    }

    return () => {
      const tableSlots = {}
      if (getSlot(slots, 'empty')) {
        // @ts-ignore
        tableSlots.empty = (...args: any[]) => getSlot(slots, 'empty', args)
      }
      if (getSlot(slots, 'append')) {
        // @ts-ignore
        tableSlots.append = (...args: any[]) => getSlot(slots, 'append', args)
      }

      return (
        <div class={[prefixCls, 'w-full']} v-loading={unref(getProps).loading}>
          {unref(getProps).showAction ? (
            <TableActions
              columns={unref(getProps).columns}
              onChangSize={changSize}
              onRefresh={refresh}
            />
          ) : null}
          <ElTable
            ref={elTableRef}
            data={unref(getProps).data}
            {...unref(getBindValue)}
          >
            {{
              default: () => renderTableColumn(),
              ...tableSlots,
            }}
          </ElTable>
          {unref(getProps).pagination ? (
            <ElPagination
              v-model:pageSize={pageSizeRef.value}
              v-model:currentPage={currentPageRef.value}
              class="mt-10px flex-wrap line-height-40px"
              {...unref(pagination)}
            >
            </ElPagination>
          ) : undefined}
        </div>
      )
    }
  },
})
</script>
